#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <sys/un.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#define TAM 2048


int main( int argc, char *argv[] ) {
	// Para TCP //
	int sockfd, puerto, n;
	struct sockaddr_in serv_addr;
	struct hostent *server;
	int terminar = 0;
	char buffer[TAM];
	// Para UDP //
	int socket_server=0;
	int resultado;
	struct sockaddr_un struct_servidor;
	
	int descriptor_socket=0;
	
	int cantidad, i,x;
	int c=0;
	int m=0;
	
	char transf[256];
	char cmd[TAM];															// Aquí voy a almacenar el comando
	char path[TAM];
	char pathviejo[TAM];
	char *p;
	char *archivo;
	
	
	FILE *pf;
	unsigned long fsize;
	int grabado=0;
	struct stat sts;
	int puertoUDP=6210;
	int size;
	int descriptor_socketUDP;
	struct hostent *serverUDP;
	
	// PARA LOG//
	int descriptor_log;
	struct sockaddr_un struct_log;
	char log_cli[TAM];														// Vamos a guardar lo que le pase el cliente de log al server
	char log_srv[TAM];														// Vamos a guardar lo que reciba el server del log del cliente
	int pid;
	char *uport[10];
	uport[0]="6209";
	char exito[]=" -- Exito\n";
	char error1[]=" -- Error, el archivo no existe\n";
	char error2[]=" -- Error en el nombre del archivo. Formato: get nombre_archivo\n";
	char error3[]=" -- Error, comando incorrecto\n";
	char error4[]=" -- Error en el nombre del archivo. Formato: put path_absoluto_archivo\n";
	time_t tiempo = time(NULL);
    struct tm *tlocal = localtime(&tiempo);
    FILE *log;
        
	
	
	
	//struct sockaddr_un struct_UDP;
	//socklen_t tamano_direccion;
	
	if ( argc < 3 ) {
		fprintf( stderr, "Uso %s host puerto\n", argv[0]);
		exit( 0 );
	}
///////////   COMIENZA CREACION Y CONFIGURACION DE SOCKETS ////////////////////////
////////// SOCKET UDP PARA TRANSFERENCIA DE ARCHIVOS /////////////////////////////
	descriptor_socketUDP = socket( AF_INET, SOCK_DGRAM, 0 );                                   //Creamos el nuevo socket UDP.
    if (descriptor_socketUDP < 0) {perror("ERROR abriendo el socket UDP");exit(0);}
    
    
    server= gethostbyname(argv[1]);                                                                             //Buscamos el host por su nombre. Si devuelve NULL, no se encontro.
    if(server == NULL)
    { perror("ERROR. No se encontro el servidor");
      exit(0);
    }
    serverUDP=server;
    
		struct sockaddr_in struct_UDP;                                                                    //Configuracion del socket UDP
        struct_UDP.sin_family = AF_INET;
        struct_UDP.sin_port = htons( puertoUDP );
        struct_UDP.sin_addr = *( (struct in_addr *)serverUDP->h_addr );
        memset( &(struct_UDP.sin_zero), '\0', 8 );
        int tamano_direccion = sizeof( struct_UDP );
    
/////////////// SOCKET PARA LOG //////////////////////
/* Creacion de socket */
	if(( descriptor_log = socket(AF_UNIX, SOCK_DGRAM, 0) ) < 0 ) 
		perror("socket" );

	/* Inicialización y establecimiento de la estructura del cliente */
	memset( &struct_log, 0, sizeof( struct_log ) );
	struct_log.sun_family = AF_UNIX;
	strncpy( struct_log.sun_path, uport[0], sizeof( struct_log.sun_path ) );

	/* Remover el nombre de archivo si existe */
  	unlink ( uport[0] );	
	
	/* Ligadura del socket de servidor a una dirección */
	if( ( bind( descriptor_log, (struct sockaddr *)&struct_log, SUN_LEN(&struct_log ))) < 0 ) {
		perror( "bind" );
		exit(1);
	}
	
/////////////////////////////////////////////////////////////	
	// PUERTO Y SOCKET STREM CON CONEXION //
	puerto = atoi( argv[2] );
	sockfd = socket( AF_INET, SOCK_STREAM, 0 );
	if ( sockfd < 0 ) {
		perror( "ERROR apertura de socket" );
		exit( 1 );
	}

	server = gethostbyname( argv[1] );
	if (server == NULL) {
		fprintf( stderr,"Error, no existe el host\n" );
		exit( 0 );
	}
	memset( (char *) &serv_addr, '0', sizeof(serv_addr) );
	serv_addr.sin_family = AF_INET;
	bcopy( (char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length );
	serv_addr.sin_port = htons( puerto );
	if ( connect( sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr ) ) < 0 ) {
		perror( "conexion" );
		exit( 1 );
	}
////////////////   FINALIZA CREACION Y CONFIGURACION DE SOCKETS   //////////////////////

//////////////////  VAMOS A CREAR EL HIJO PARA QUE SE ENCARGUE DEL LOG DEL PADRE  /////////////////////////
		pid = fork(); 																
		if ( pid < 0 ) {
			perror( "fork" );
			exit( 1 );
		}
		
		if (pid==0)   // Proceso hijo para el log
		{	log = fopen ("comunicacion.log","a+");
			
			while(1)	// El servidor de log se queda escuchando los mensajes y almacenando en el archivo comunicacion.log				
			{
			memset(log_srv,0,TAM);
			resultado = recvfrom ( descriptor_log, (void *)log_srv, TAM, 0, (struct sockaddr *) &struct_log, &tamano_direccion );  // Recibimos lo que nos manda el cliente para guardar en el log
			if( resultado < 0 ) {perror( "recepción" );exit( 1 );}
			fwrite (log_srv , 1 , sizeof(log_srv) , log );
			//printf("Recibi para el log: %s",log_srv);
			}
		}
///////////////////////////////// HIJO DONDE ANDMINISTRAMOS EL LOG  /////////////////////////////////////////		



	while(1) {

		
		printf( "# " );
		memset( buffer, '\0', TAM );
		memset( transf, '\0', TAM );
		fgets( buffer, TAM-1, stdin );
		while(!strcmp(buffer,"\n"))										// Me quedo aquí hasta que ingrese algo distinto de enter
		{printf( "# " );
		 memset( buffer, 0, TAM );
		 memset( transf, 0, TAM );
		 memset( log_cli, '\0', TAM );
		 fgets( buffer, TAM-1, stdin );}
		
		
		strftime(log_cli,TAM,"%d/%m/%y %H:%M:%S -- ",tlocal);
        
		
		strcat(log_cli,buffer);
		strtok(log_cli,"\n");
		
		
		strcpy(cmd,buffer);													// Una vez que se produce la lectura del buffer vamos a analizar que ingreso
		strcpy(transf,buffer);
		strtok(cmd," ");
		
/////////////////  CHEQUEAMOS SI FUE PUT EL COMANDO INGRESADO   ////////////////////
		if(!strcmp("put",cmd))
			{
			 
			 archivo= strstr(transf," ");
			 archivo++;											
			 archivo[strlen(archivo)-1] = '\0';
			
			 if(archivo[0]=='/')
			 {
			 if (x=stat(archivo, &sts)==0)
				{
//////////////  ARCHIVO A TRANSFERIR SI EXISTE, MANDAMOS EL COMANDO ///////////
				 memset(buffer,0,TAM);strcpy(buffer,"put");strcat(buffer," ");strcat(buffer,archivo);
				 n = write( sockfd, buffer, strlen(buffer));
				 if ( n < 0 ) 
					{
					perror( "escritura de socket" );
					exit( 1 );
					}
					memset( buffer, '\0', TAM );
					n = read( sockfd, buffer, TAM );
					if ( n < 0 ) 
					{
					perror( "lectura de socket" );
					exit( 1 );
					}
					printf( "%s\n", buffer );
					
				
				// printf("Existe %s?= %d\n",archivo,x);
				 pf = fopen(archivo,"rb"); 								// Abro el archivo, acá podria hacer el control de error.
				 fseek(pf,0,SEEK_END);
				 fsize = ftell(pf);
				 rewind(pf);
				 
				 
				sprintf(buffer,"%lu",fsize);
				//printf("File contains %s bytes!\n", buffer);
				n = sendto( descriptor_socketUDP, buffer, TAM, 0, (struct sockaddr *)&struct_UDP, tamano_direccion );
				if(n<0){perror("Escritura");exit(1);}
                                                        
              //  printf("mande tamano en buffer %s\n",buffer);
                                
				int enviados=0;int leidos=0;
				while(enviados!=fsize){
					
				n = recvfrom( descriptor_socketUDP, buffer, TAM, 0, (struct sockaddr *)&struct_UDP,  (socklen_t*)&tamano_direccion );
                        if(n<0){perror("ERROR en lectura de socket");}
                     //   printf("Recibi mensaje de inicializacion\n");
                        
				memset( transf, 0, 256 );                                    
				leidos=fread(transf,1,256,pf);                                                      
				n=sendto( descriptor_socketUDP,(void *) transf, leidos, 0, (struct sockaddr *)&struct_UDP,  tamano_direccion );
				if(n<0){perror("Escritura");exit(1);}
			//	printf("bytes read para escribir %d, bytes written %d, SIZE %lu\n",leidos,enviados,fsize);
				enviados=enviados+leidos;                                                                           
				}
				strcat(log_cli,exito);
				
				fclose(pf);
				}
				else{strcat(log_cli,error1);printf("Comando Incorrecto: El archivo a transferir no existe. VER ARCHIVO comunicacion.log\n");}
				
				}
				else{strcat(log_cli,error4);printf("Comando Incorrecto: Ingrese un path absoluto correcto al archivo. VER ARCHIVO comunicacion.log\n");}
			}
///////////////    NO FUE PUT, FUE ALGUN OTRO COMANDO   //////////////////////////////
			else{	
				
//////////////     FUE GET   //////////////////////////
				if(!strcmp("get",cmd))
					{
					
						
					//printf("soy get! buffer %s\n",buffer);
					 if(strstr(buffer,"/")==NULL)
					 {	 
						 n = write( sockfd, buffer, strlen(buffer));
						 archivo= strstr(transf," ");
						  archivo++;											
						  archivo[strlen(archivo)-1] = '\0';	
						 
						 if ( n < 0 ) 
							{
							perror( "escritura de socket" );
							exit( 1 );
							}
							memset( buffer, '\0', TAM );
							n = read( sockfd, buffer, TAM );
							if ( n < 0 ) 
							{
							perror( "lectura de socket" );
							exit( 1 );
							}
							//printf( "%s\n", buffer );
////////////  VEMOS SI EL ARCHIVO EXISTIA O NO, PARA ESCUCHAR LA TRANSFERENCIA   ////////////////

							if(strlen(buffer)==22)						// El archivo NO EXISTE //
							{
								strcat(log_cli,error1);
								printf("Comando Incorrecto: El archivo a transferir no existe. VER ARCHIVO comunicacion.log\n");
							}
							else
							{											// El archivo SI EXISTE //
								strcat(log_cli,exito);				// Agregamos al log que el comando Get se ejecutó con éxito //
								
								if(strcmp(pathviejo,path)){path[strlen(path)-1] = '\0';strcat(path,"/");strcat(path,archivo);}
								//printf("path para ver tamano %s\n",path);
								strcpy(pathviejo,path);
								pf = fopen(path, "rb");
								fseek(pf,0,SEEK_END);
								fsize = ftell(pf); // Vemos el tamano del archivo
								rewind(pf);
								fclose(pf);
								pf = fopen(archivo, "w+b");
							//	printf("tamano %lu, grabado %lu\n",fsize,grabado);
								
								
								memset(buffer,0,TAM);
							//	printf("Por mandar mensaje de inicializacion\n");
								n= sendto( descriptor_socketUDP, (void *)buffer, TAM, 0, (struct sockaddr *)&struct_UDP, tamano_direccion ); 
							//	printf("Esperando tamano\n");
								n= recvfrom( descriptor_socketUDP, (void *)buffer, TAM, 0, (struct sockaddr *)&struct_UDP, (socklen_t*)&tamano_direccion );
								if ( n < 0 ) {perror( "Lectura en socket" );}
								 size=atoi(buffer);
							//	printf("Recibi tamano %d\n",size);
								
								
								m=0;
								while(grabado < size)
										{
											
										memset(buffer,0,TAM);
									//	printf("Por mandar mensaje de inicializacion\n");
										n= sendto( descriptor_socketUDP, (void *)buffer, TAM, 0, (struct sockaddr *)&struct_UDP, tamano_direccion ); //Hacemos una primera conexion con el servidor con el buffer en blanco                 
										if ( n < 0 ) {perror( "Escritura en socket" );}	
											
											
								
									//	printf("ESCUCHANDO! grabado: %d, size %d\n",grabado,size);
										memset(transf,0,256);
									//	printf("ESPERO! TODAVIA NO RECIBI\n");
										
										m = recvfrom( descriptor_socketUDP, (void *)transf, 256, 0, (struct sockaddr *) &struct_UDP, &tamano_direccion );
										if (m < 0) error("ERROR reading from socket");
										m = fwrite(transf,1,m,pf);
									//	printf("escribi!! %d\n",m);
										if (m < 0) error("ERROR writing in file");
										grabado +=m;
										}
								memset(buffer,0,TAM);
								memset(transf,0,TAM);
								grabado=0;
								fclose(pf);
								//read( sockfd, buffer, TAM );
								
							}
							
							
					 
					 
					 }
					 else
					 {	 // Como es un get INCORRECTO le agregamos Error en el nombre
						 strcat(log_cli,error2);
						 printf("Comando Incorrecto: Coloque solo el nombre del archivo que desea bajar del servidor. VER ARCHIVO comunicacion.log\n");
					 }
					
					
					
					}
					else{
//////////////   FUE OTRO COMANDO   ///////////////////////
					if(!strcmp("cd",cmd)){c=1;}							// Si es CD ponemos una bandera en 1 para luego analizarla
					n = write( sockfd, buffer, strlen(buffer) );
			
					if ( n < 0 ) 
					{
					perror( "escritura de socket" );
					exit( 1 );
					}
					
					// Verificando si se escribió: fin
					buffer[strlen(buffer)-1] = '\0';
					if( !strcmp( "fin", buffer ) ) 
					{
					terminar = 1;
					}
					
					memset( buffer, '\0', TAM );
					n = read( sockfd, buffer, TAM );
						
					
					
					if(c==1&&(strcmp("cd",buffer))>0){strcpy(path,buffer);c=0;}	// Si es 1, guardamos lo que tenemos en buffer que es el path del directorio actual en path
					if ( n < 0 ) {
					perror( "lectura de socket" );
					exit( 1 );
					}
					
					
					printf( "%s\n", buffer );
					
					if(strstr(buffer,"incorrecto")==NULL){strcat(log_cli,exito);}			// Analizamos que pasó con la ejecución del comando, si fue exitosa o no para avisarle al log
					else{strcat(log_cli,error3);}
					
		
					if( terminar ) 
						{
						fclose(log);
						printf( "Finalizando ejecución\n" );
						exit(0);
						}
			
			
			
			
				}
				
				
		
		}
		
		// Mandamos la respuesta de los comandos //
					
					resultado = sendto( descriptor_log, log_cli, strlen(log_cli), 0, (struct sockaddr *)&struct_log, sizeof(struct_log) );
					if( resultado < 0 ) {perror( "sendto" );exit( 1 );}
		
	}
	return 0;
} 
